package com.googlecode.struts2scope.util;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.StringTokenizer;

public class BeanProvider {

	private static String GET = "get";
	private static String IS = "is";
	private static String HAS = "has";

	public static boolean setProperty(Object object, String property,
			Object value) {

		if ((property == null) || (object == null)) {
			return false;
		}

		// Separa as propridades por ponto: ( "person.name.first" ->
		// "person","name","first" -> getPerson().getName().getFirst() )
		StringTokenizer st = new StringTokenizer(property, ".");

		if (st.countTokens() == 0) {
			return false;
		}

		Object current = object;

		try {
			// Loop through properties in chain.
			for (int i = 0; st.hasMoreTokens(); i++) {
				String currentPropertyName = st.nextToken();

				if (i < st.countTokens()) {
					// Aqui ainda é um metodo getter.
					current = invokeProperty(current, currentPropertyName);
				} else {
					// Aqui chama o metodo setter.
					try {
						// Chama o setter via classes de Java Bean do Java.
						PropertyDescriptor pd = new PropertyDescriptor(
								currentPropertyName, current.getClass());
						pd.getWriteMethod().invoke(current,
								new Object[] { value });

						return true;
					} catch (Exception e) {
						return false;
					}
				}
			}

			// Propriedade setada.
			return true;
		} catch (NullPointerException e) {
			// Caso alguma propriedade retorne null.
			return false;
		}
	}

	public static Object getProperty(Object object, String property) {

		if ((property == null) || (object == null)) {
			return null;
		}

		// Separa as propridades por ponto: ( "person.name.first" ->
		// "person","name","first" -> getPerson().getName().getFirst() )
		StringTokenizer st = new StringTokenizer(property, ".");

		if (st.countTokens() == 0) {
			return null;
		}

		// 
		Object result = object;

		try {
			// 
			while (st.hasMoreTokens()) {
				String currentPropertyName = st.nextToken();

				// 
				result = invokeProperty(result, currentPropertyName);
			}

			// 
			return result;
		} catch (NullPointerException e) {
			// Caso alguma propriedade retorne null.
			return null;
		}
	}

	/**
	 * Converte o nome da propriedade para metodo get, is ou has: ( "something" -> "getSomething(), isSomething() ou hasSomething()" )
	 */
	public static String createMethodName(String prefix, String propertyName) {
		return prefix + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
	}

	/**
	 * Excuta o getter do metodo/field de um objeto. Tentativas na ordem:
	 * obj.getProperty(), obj.isProperty(), obj.property(), obj.property.
	 */
	private static Object invokeProperty(Object obj, String property) {

		if ((property == null) || (property.length() == 0)) {
			return null;
		}

		Class<?> cls = obj.getClass();
		Object[] parameters = {};
		Class<?>[] classParameters = {};

		try {
			// Tenta executar metodo get. object.getProperty()
			Method method = cls.getMethod(createMethodName(GET, property), classParameters);

			return method.invoke(obj, parameters);
		} catch (Exception e1) {
			try {
				// Tenta metodo is. object.isProperty()
				Method method = cls.getMethod(createMethodName(IS, property),
						classParameters);

				return method.invoke(obj, parameters);
			} catch (Exception e2) {
				try {
					// Tenta direto pelo nome da propriedade. object.property()
					Method method = cls.getMethod(property, classParameters);

					return method.invoke(obj, parameters);
				} catch (Exception e3) {
					try {
						// Agora tenta direto pelo atributo.
						Field field = cls.getField(property);

						return field.get(obj);
					} catch (Exception e4) {
						// Nenhum possibilidade.
						return null;
					}
				}
			}
		}
	}
}
